<?php

namespace Icsoc\DataBundle\Model;

use GuzzleHttp\Client;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Constraints\File;
use Symfony\Component\Validator\Constraints\Type;
use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
 * 语音相关
 * Class SoundModel
 * @package Icsoc\DataBundle\Model
 */
class SoundModel
{
    /** @var ContainerInterface */
    private $container;

    /** @var \Doctrine\DBAL\Connection  */
    private $dbal;

    /**
     * @param ContainerInterface $container
     */
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
        $this->dbal = $container->get('doctrine.dbal.default_connection');
    }

    /**
     * 语音列表
     * @param array $param 参数，格式为
     *                     array(
     *                         vcc_code,//企业代码
     *                         info,// array 提供分页信息
     *                     )
     * @return array
     */
    public function soundList(array $param = array())
    {
        $vcc_code = empty($param['vcc_code']) ? 0 : $param['vcc_code'];
        $info = empty($param['info']) ? '' : $param['info'];

        $msg = $vid = $this->container->get('icsoc_data.validator')->checkVccCode($vcc_code);
        if (!empty($msg) && is_array($msg)) {
            return $msg;
        }
        //分页搜索相关信息；
        $addInfo = array();
        if (!empty($info)) {
            $addInfo = json_decode($info, true);
            if (json_last_error()) {
                return array('code' => 403, 'message'=>'info格式非json');
            }
        }
        $where = '';
        if (isset($addInfo['filter'])) {
            if (isset($addInfo['filter']['keyword']) && !empty($addInfo['filter']['keyword'])) {
                $where.=" AND name  LIKE '%".$addInfo['filter']['keyword']."%' ";
            }
            $offset = isset($addInfo['filter']['select_type']) ? 'select_type' : 'sound_type';
            if (isset($addInfo['filter'][$offset]) && !empty($addInfo['filter'][$offset])) {
                $where.=" AND sound_type = '".(int)$addInfo['filter'][$offset]."'";
            }
        }
        //查出总计的条数
        $count = $this->dbal->fetchColumn(
            'SELECT count(*) '.
            'FROM win_sounds '.
            'WHERE vcc_id = :vid '.$where,
            array('vid'=>$vid)
        );
        $page = $this->container->get("icsoc_data.helper")->getPageInfo($count, 'win_sounds', $addInfo);
        $list = $this->dbal->fetchAll(
            'SELECT id,name,remark,address,sounds_address,add_time,sound_type '.
            'FROM win_sounds '.
            'WHERE vcc_id = :vcc_id '.$where.
            'ORDER BY '.$page['sort'].' '.$page['order'].' LIMIT '.$page['start'].','.$page['limit'],
            array('vcc_id' => $vid)
        );
        return array('code'=>200, 'message'=>'ok', 'total' => $count,'data'=>$list, 'totalPage' => $page['totalPage']);
    }

    /**
     * 删除语音
     * @param array $param  vcc_code 企业代码, sound_id array 需要删除的id；
     * @return array|string
     */
    public function delSound(array $param = array())
    {
        $vcc_code = empty($param['vcc_code']) ? 0 : $param['vcc_code'];
        $sound_arr = empty($param['sound_id']) ? array() : $param['sound_id'];
        //vcc_code 验证
        $msg = $vid = $this->container->get('icsoc_data.validator')->checkVccCode($vcc_code);
        if (!empty($msg) && is_array($msg)) {
            return $msg;
        }

        if (empty($sound_arr)) {
            return array('code'=>405, 'message'=>'sound_id为空');
        }

        $type = new Type(array('type'));
        $type->type = 'numeric';
        $type->message = "sound_id不是数字";
        $errorMsg = array();
        foreach ($sound_arr as $sound_id) {
            $error_list = $this->container->get('validator')->validateValue($sound_id, array($type));
            if (count($error_list) > 0) {
                $errorMsg[] = array('code'=>403, 'message'=>$error_list[0]->getMessage(), 'sound_id'=>$sound_id);
                continue;
            }
            if (!is_numeric($sound_id)) {
                $errorMsg[] = array('code'=>403, 'message'=>'sound_id不是数字', 'sound_id'=>$sound_id);
            }
            $number =  $this->dbal->delete("win_sounds", array('id' => $sound_id, 'vcc_code'=>$vcc_code));
            //是否需要把语音文件删除掉；待讨论；
            if ($number == 0) {
                $errorMsg[] = array('code'=>404, 'message'=>"sound_id:$sound_id 记录不存在",'sound_id'=>$sound_id);
            } else {
                $errorMsg[] = array('code'=>200, 'message'=>"OK",'sound_id'=>$sound_id);
            }
        }
        return array('code'=>500, 'message'=>'总结果', 'data'=>$errorMsg);
    }

    /**
     * 上传语音
     * @param Request $request
     * @return array|string
     */
    public function upload(Request $request)
    {
        $vcc_code =  $request->get('vcc_code', 0);
        $name = $request->get('name', '');
        $remark = $request->get('remark', '');
        $sound = $request->files->get("sound");
        $soundType = $request->get('sound_type', 6);//语音类型；缺省值 其他
        //vcc_code 验证
        $msg = $vid = $this->container->get('icsoc_data.validator')->checkVccCode($vcc_code);
        if (!empty($msg) && is_array($msg)) {
            return $msg;
        }
        //验证name
        $msg = $this->container->get('icsoc_data.validator')->isEmptyName($name, 403, '语音名称为空');
        if (!empty($msg) && is_array($msg)) {
            return  $msg;
        }
        //验证是否上传文件；
        if (empty($sound)) {
            return array('code'=>409, 'message'=>'没有上传语音');
        }
        $res = $this->uploadFile($sound, $vid);
        if ($res['code'] != 200) {
            return $res;
        }

        $this->dbal->beginTransaction();
        try {
            $data = $res['data'];
            $data['vcc_id'] = $vid;
            $data['name'] = $name;
            $data['remark'] = $remark;
            $data['vcc_code'] = $vcc_code;
            $data['sound_type'] = $soundType;
            $data['add_time'] = date("Y-m-d H:i:s");
            $this->dbal->insert('win_sounds', $data);
            $sound_id = $this->dbal->lastInsertId();
            if ($sound_id) {
                //$result = $this->addToChangelogAndSendNoticeToZmqServer($sound_id, $vid);
                $result = $this->sendNoticeMessage($sound_id, $vid);
                if ($result === false) {
                    $this->dbal->rollBack();

                    return array(
                        'code' => 408,
                        'message' => '数据保存失败',
                    );
                }

                $this->dbal->commit();

                return array(
                    'code' => 200,
                    'message' => 'ok',
                    'path' => $data['sounds_address']
                );
            } else {
                $this->dbal->rollBack();

                return array(
                    'code' => 408,
                    'message' => '数据保存失败',
                );
            }
        } catch (Exception $e) {
            $this->dbal->rollback();

            return array(
                'code' => 408,
                'message' => '数据保存失败',
            );
        }
    }

    /**
     * 修改语音
     * @param Request $request
     * @return array|string
     */
    public function updateSound(Request $request)
    {
        $vcc_code =  $request->get('vcc_code', 0);
        $name = $request->get('name', '');
        $remark = $request->get('remark', '');
        $sound = $request->files->get("sound");
        $soundType = $request->get('sound_type', 6);//语音类型；缺省值 其他
        $soundId = $request->get('sound_id', 0);
        //vcc_code 验证
        $msg = $vid = $this->container->get('icsoc_data.validator')->checkVccCode($vcc_code);
        if (!empty($msg) && is_array($msg)) {
            return $msg;
        }

        //验证name
        $msg = $this->container->get('icsoc_data.validator')->isEmptyName($name, 403, '录音名称为空');
        if (!empty($msg) && is_array($msg)) {
            return  $msg;
        }

        if (empty($soundId)) {
            return array('code'=>410, 'message'=>'sound_id 不能为空');
        }

        //验证是否上传文件；
        if (!empty($sound)) {
            $filename = $this->dbal->fetchColumn("SELECT address FROM win_sounds WHERE id = ?", array($soundId));
            $filename = pathinfo($filename, PATHINFO_BASENAME);
            $res = $this->uploadFile($sound, $vid, $filename);
            if ($res['code'] == 200) {
                $data = $res['data'];
            } else {
                return $res;
            }
        }

        $this->dbal->beginTransaction();
        try {
            $data['vcc_id'] = $vid;
            $data['name'] = $name;
            $data['remark'] = $remark;
            $data['vcc_code'] = $vcc_code;
            $data['sound_type'] = $soundType;
            $data['add_time'] = date("Y-m-d H:i:s");
            $num = $this->dbal->update('win_sounds', $data, array('id'=>$soundId));
            if ($num) {
                //$result = $this->addToChangelogAndSendNoticeToZmqServer($soundId, $vid, 'update');
                $result = $this->sendNoticeMessage($soundId, $vid, 'update');
                if ($result === false) {
                    $this->dbal->rollBack();

                    return array(
                        'code' => 408,
                        'message' => '数据保存失败',
                    );
                }

                $this->dbal->commit();

                return array(
                    'code' => 200,
                    'message' => 'ok',
                );
            } else {
                $this->dbal->rollBack();

                return array(
                    'code' => 408,
                    'message' => '数据保存失败',
                );
            }
        } catch (\Exception $e) {
            $this->dbal->rollBack();

            return array(
                'code' => 408,
                'message' => '数据保存失败',
            );
        }
    }

    /**
     * @param array $param
     * @return mixed
     */
    public function getSoundInfo(array $param = array())
    {
        $vcc_code = empty($param['vcc_code']) ? 0 : $param['vcc_code'];
        $sound_id = empty($param['sound_id']) ? 0 : $param['sound_id'];
        //vcc_code 验证
        $msg = $vid = $this->container->get('icsoc_data.validator')->checkVccCode($vcc_code);
        if (!empty($msg) && is_array($msg)) {
            return $msg;
        }
        $data = $this->dbal->fetchAssoc(
            "SELECT `name`,remark,address,sound_type,id
            FROM win_sounds WHERE id = :sound_id AND vcc_code = :vcc_code",
            array('sound_id'=>$sound_id, 'vcc_code'=>$vcc_code)
        );
        return $data;
    }

    /**
     * 上传语音文件
     *
     * @param UploadedFile $sound 要上传的文件的对象
     * @param int $vid 企业ID
     *
     * @return array
     */
    private function uploadFile($sound, $vid, $filename = '')
    {
        //验证 sound
        $valiFile = new File();
        $valiFile->mimeTypes = array('audio/x-wav','audio/mpeg'); //验证是否为语音格式;
        $valiFile->mimeTypesMessage = $this->container->get('translator')
            ->trans('The voice is not in the correct format');
        $error_list = $this->container->get("validator")->validateValue($sound, $valiFile);
        if (count($error_list) > 0) {
            $error_msg = $error_list[0]->getMessage();
            return array('code'=>404, 'message'=>$error_msg);
        } else {
            //开始上传文件
            $extension = $sound->getClientOriginalExtension();
            //如果是修改;就不采用这中方式
            if (empty($filename)) {
                $filename = $this->getFileName($vid, $extension);
            }
            $tmpFileName = $filename;
            $relativePath = 'ccod/'.$vid.'/'.$filename;
            $filepath = $this->container->getParameter('sound_local_upload_path').'/'.$vid;
            if ($extension != 'wav') {
                //不是wav都需要转换成wav格式
                $filename = str_replace($extension, 'wav', $filename);
            }
            $absulateFilePath = $filepath.'/tmp'.$filename; //转换成wav
            $targetFilePath = $filepath.'/'.$filename;//最终目标地址;
            $absulateTmpFilePath = $filepath.'/orgin_'.$tmpFileName;
            try {
                $sound->move($filepath, 'orgin_'.$tmpFileName);
                $command = 'sox '.$absulateTmpFilePath.' -c 1 -r 8000 -b 16 '.$absulateFilePath;
                $this->container->get("logger")->info($command); //记录一下
                $result = @exec($command);

                //非空表明有输出信息，有错误
                if (!empty($result)) {
                    return array(
                        'code' => 405,
                        'message' => $result
                    );
                }

                //转化后的文件不存在
                if (!file_exists($absulateFilePath)) {
                    return array(
                        'code' => 406,
                        'message' => "语音转换失败"
                    );
                }

                //写入alioss;
                $stream = fopen($absulateFilePath, 'r');
                $alioss = $this->container->get("icsoc_filesystem")
                    ->putStream($targetFilePath, $stream);
                $fs = new Filesystem();
                if (!$alioss) {
                    $fs->remove(array($targetFilePath, $relativePath));//删除文件;
                    return array(
                        'code' => 409,
                        'message' => '写入alioss失败',
                    );
                }
                $fs->remove(array($absulateFilePath));
            } catch (Exception $e) {
                return array(
                    'code' => 405,
                    'message' => $sound->getErrorMessage()
                );
            }
            $data = array();
            $data['address'] = $targetFilePath;
            $data['sounds_address'] = $relativePath;
            return array('code'=>'200', 'data'=>$data);
        }
    }

    /**
     * 获取文件名
     * @param $vid
     * @param string $extension
     * @return string
     */
    private function getFileName($vid, $extension = 'wav')
    {
        $maxid = $this->dbal->fetchColumn(
            'SELECT max(id) '.
            'FROM win_sounds '.
            'WHERE vcc_id = :vcc_id ',
            array('vcc_id'=>$vid)
        );
        $maxid = $maxid ? $maxid+1 : 1;
        $filename = 'sound'.$vid.'_'.$maxid.'.'.$extension;
        return $filename;
    }

    /**
     * 拷贝文件到远程服务器;
     * @param array $data
     * @param int $vid
     * @return int
     *
     * @deprecated Since manage3.0.0

    private function copyFile(array $data, $vid)
    {
    /* 调用远程system文件拷贝语音 *
    $win_ip  = $this->container->getParameter('win_ip');
    $win_web_port = $this->container->getParameter('win_web_port');
    $win_system_name = $this->container->getParameter('win_system_name');
    $ch = curl_init("http://$win_ip:$win_web_port/$win_system_name/system.php?act=sound");
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    if (class_exists('CurlFile')) {
    $file = new \CURLFile($data['address']);
    } else {
    $file = '@'.$data['address'];
    }
    curl_setopt($ch, CURLOPT_POSTFIELDS, array(
    'vcc_id' => $vid,
    'sound' => $file,
    ));
    $exec_result = curl_exec($ch);
    $exec_result = json_decode($exec_result, true);
    $code = isset($exec_result['code']) ? $exec_result['code'] : 0;
    return $code;
    }*/

    /**
     * 获取企业下的所有语音，格式为数组
     *
     * @param int $vcc_id
     *
     * @return array|bool
     */
    public function getSoundsArray($vcc_id)
    {
        if (empty($vcc_id)) {
            return false;
        }

        $result = $this->dbal->fetchAll('SELECT * FROM win_sounds WHERE vcc_id=:vcc_id', array(':vcc_id' => $vcc_id));

        return $result;
    }

    /**
     * 获取企业下的某种语音，格式为数组
     *
     * @param int $vcc_id
     * @param int $type
     * @return array|bool
     */
    public function getSoundsTypeArray($vcc_id, $type)
    {
        if (empty($vcc_id)) {
            return false;
        }

        $result = $this->dbal->fetchAll('SELECT * FROM win_sounds WHERE vcc_id=:vcc_id AND sound_type=:sound_type', array(':vcc_id' => $vcc_id,':sound_type' => $type));

        return $result;
    }

    /**
     * 获取某个语音文件，格式为数组
     *
     * @param int $id
     *
     * @return array|bool
     */
    public function getTheSoundsArray($id)
    {
        if (empty($id)) {
            return false;
        }

        $result = $this->dbal->fetchAssoc('SELECT * FROM win_sounds WHERE id=:id', array(':id' => $id));

        return $result;
    }

    /**
     * 获取满意度语音
     *
     * @param $vccId
     * @return array|bool
     */
    public function getEvaluateSounds($vccId)
    {
        if (empty($vccId)) {
            return false;
        }

        $result = $this->dbal->fetchAll(
            'SELECT id,address,name FROM win_sounds WHERE vcc_id=:vcc_id AND sound_type=:sound_type',
            array(':vcc_id' => $vccId,':sound_type' => 2)
        );
        $prefix = $this->container->getParameter('sound_local_upload_path');
        foreach ($result as $key => $val) {
            $result[$key]['sounds_address'] = str_replace($prefix, 'ccod', $val['address']);
        }

        return $result;
    }

    private function sendNoticeMessage($soundId, $vcc_id, $logAction = 'create')
    {
        if (empty($soundId)) {
            return false;
        }

        $logger = $this->container->get('logger');
        //添加更新记录表
        $changelog = array(
            'vcc_id' => $vcc_id,
            'log_type' => 'sound',
            'log_action' => $logAction,
            'table_id' => $soundId
        );
        $ret = $this->dbal->insert('cc_cti_change_logs', $changelog);
        $nid = $this->dbal->lastInsertId();
        if ($ret <= 0) {
            $logger->critical(sprintf('Insert value %s to table cc_cti_change_logs failed.', json_encode($changelog)));
            return false;
        }

        $msg = json_encode(array('nid' => intval($nid)));
        try{
            $culrClient = new Client(
                array(
                    'base_uri'=>$this->container->getParameter('sound_distribute_url'),
                    'timeout'=>10
                )
            );
            $response = $culrClient->request('GET', '',  array('query'=>array('text'=>$msg)));
            $res = json_decode($response->getBody(), true);
            if (json_last_error() === JSON_ERROR_NONE && isset($res['ret']) && $res['ret'] == 0) {
                return true;
            } else {
                $logger->critical(sprintf('调用语音分发接口错误,返回【%s】', $response->getBody()));
            }
        } catch (\Exception $e) {
            $logger->critical(sprintf('调用语音分发接口错误【%s】', $e->getMessage()));
        }

        return false;
    }

    /**
     * 添加更新记录表，以及通知分发服务有更新
     *
     * @param int $soundId      更新的语音ID
     * @param int $vcc_id       企业id
     * @param string $logAction 操作(create,update)
     *
     * @return bool
     */
    public function addToChangelogAndSendNoticeToZmqServer($soundId, $vcc_id, $logAction = 'create')
    {
        if (empty($soundId)) {
            return false;
        }

        $logger = $this->container->get('logger');
        //添加更新记录表
        $changelog = array(
            'vcc_id' => $vcc_id,
            'log_type' => 'sound',
            'log_action' => $logAction,
            'table_id' => $soundId
        );
        $ret = $this->dbal->insert('cc_cti_change_logs', $changelog);
        $nid = $this->dbal->lastInsertId();
        if ($ret <= 0) {
            $logger->critical(sprintf('Insert value %s to table cc_cti_change_logs failed.', json_encode($changelog)));
            return false;
        }

        //发送通知
        $zmqServer = $this->container->getParameter('cti_zmq_server');
        $zmqPort = $this->container->getParameter('cti_zmq_port');

        try {
            $context = new \ZMQContext();
            $requester = new \ZMQSocket($context, \ZMQ::SOCKET_REQ);
            $requester->connect("tcp://$zmqServer:$zmqPort");
            $msg = json_encode(array('nid' => intval($nid)));
            $requester->send($msg);
            $logger->info(sprintf('Send the request to the server %s:%s, the msg is %s.', $zmqServer, $zmqPort, $msg));

            $poll = new \ZMQPoll();
            $poll->add($requester, \ZMQ::POLL_IN);
            $readable = $writeable = array();
            $events = $poll->poll($readable, $writeable, 5000);
            $errors = $poll->getLastErrors();

            //存在错误
            if (count($errors) > 0) {
                foreach ($errors as $error) {
                    $logger->critical(sprintf("Error polling object, the error is %s", $error));

                    return false;
                }
            }

            //收到消息
            if ($events > 0) {
                foreach ($readable as $socket) {
                    if ($socket === $requester) {
                        $reply = $requester->recv();
                    }
                }
            } else {
                return false;
            }

            if (empty($reply)) {
                $logger->critical(sprintf('The reply of the request is empty, needs json.'));

                return false;
            }
            $replyArray = json_decode($reply, true);
            $jsonLastError = json_last_error();
            if ($jsonLastError != JSON_ERROR_NONE) {
                $logger->critical(sprintf('Json decode the reply %s failed, the error is %s.', $reply, $jsonLastError));

                return false;
            }

            $ret = isset($replyArray['ret']) ? $replyArray['ret'] : '-1';
            if ($ret === 0) {
                return true;
            } else {
                $logger->critical(sprintf('The ret of the reply %s is not 0, the reply is %s.', $ret, $reply));

                return false;
            }
        } catch (\ZMQPollException $e) {
            $logger->critical(sprintf('Poll failed: %s', $e->getMessage()));

            return false;
        }
    }
}
